Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
C# / VB.NET - Da byte[] ad una classe
Forum - C# / VB.NET - Da byte[] ad una classe

Avatar
xeeynamo (Normal User)
Pro


Messaggi: 66
Iscritto: 14/03/2008

Segnala al moderatore
Postato alle 20:38
Mercoledì, 18/11/2009
Ciao!
Ho un problema nel convertire un array di byte in un semplice oggetto... Ad esempio ho una classe di questo tipo
Codice sorgente - presumibilmente C#

  1. public class Ciao{
  2.     public byte v1;
  3.     public int v2, v3;
  4. }


e il codice che tento di fare è un qualcosa di molto simile:
Codice sorgente - presumibilmente C# / VB.NET

  1. Ciao ciao;
  2. FileStream file = new FileStream("file.bin", FileMode.Open);
  3. byte[] data = new data[9];
  4. file.Read(data, 0, data.Length);
  5. ciao = (Ciao)data; // La parte che ovviamente non mi funziona


Ad esempio nella classe Ciao ho un byte e due int, con un totale di 9 bytes, quindi ho bisogno di leggere i 9 byte dal file in lettura e assegnarlo completamente alla classe ciao. Lo sò che potrei fare tipo ciao.v1 = file.ReadByte() però dato che devo gestire moltissimi dati (sarà 3.9kb in totale) verrebbe un codice troppo lungo e poco performante. Consigli? Aiuti? Dritte?

PM Quote
Avatar
TheKaneB (Member)
Guru^2


Messaggi: 1792
Iscritto: 26/06/2009

Segnala al moderatore
Postato alle 21:25
Mercoledì, 18/11/2009
noooo!!! avevo scritto un post molto dettagliato ma ho perso tutto perchè mi è scaduta la sessione di login... X_X

sono troppo pigro per riscriverlo per intero con la stessa cura XD

succo del discorso: il compilatore per ottimizzare la rapidità degli accessi alla memoria, riordina i membri di una classe o di una struct, sposta gli offset dei membri, in modo tale da renderli multipli dell'unità di memorizzazione di base (che dipende dall'architettura, generalmente  4 bytes su architetture a 32bit), e inserisce dei byte di padding (inutilizzati) nei buchi rimasti dentro la struct/classe.

Quindi i dati che leggi da file oltre ad avere una dimensione inferiore a quella della classe (a causa dei bytes di padding), hanno anche un ordine diverso rispetto a quello che hai specificato tu nel sorgente.
Il modo più semplice ed efficiente per ovviare al problema è quello di anteporre alla dichiarazione della struct/classe l'attributo __packed (questo su GCC; non ricordo l'equivalente per i compilatori microsoft). In questo modo il compilatore sa che deve mantenere la struct/classe nella forma esatta del sorgente.

Ciao ;)

PM Quote
Avatar
xeeynamo (Normal User)
Pro


Messaggi: 66
Iscritto: 14/03/2008

Segnala al moderatore
Postato alle 22:15
Mercoledì, 18/11/2009
Uffi peccato, una risposta più approfondita mi sarebbe piaciuta di più XD. Cmq il concetto l'ho capito :). Ho fatto una breve ricerca su internet e purtroppo non ho trovato niente su una cosa equivalente al package sul C#... A questo punto mi dovrei creare una libreria in C che svolge questa funzione e poi richiamarla dal mio programma in C# giusto? Ma non ci sono sistemi più semplici? Meno codice ingombro nel mio progetto meglio è :D

PM Quote
Avatar
Il Totem (Admin)
Guru^2


Messaggi: 3635
Iscritto: 24/01/2006

Segnala al moderatore
Postato alle 11:34
Giovedì, 19/11/2009
In .NET l'attributo si chiama StructLayout ed è applicabile solo alle strutture. Ho scritto un articolo tempo fa:
http://www.pierotofy.it/pages/guide_tutorials/Visual_Basic ...

Ad ogni modo, se in C questo è possibile, in .NET no. Anche ponendo quell'attributo non fai altro che riordinare i membri, ma rimane il fatto che non puoi convertire un array di byte in una struttura (o classe).
Per poterlo fare, devi definire una nuova versione dell'operatore CType per la tua struttura:
http://totem.altervista.org/guida/versione3/A27%20-%20Gli% ...

Ma anche questo è meno efficiente della serializzazione. Questa tecnica ti permette di convertire qualsiasi tipo di dato in uno stream di dati binari, e di riconvertirli in oggetto successivamente usando non più di cinque o sei righe di codice:
http://totem.altervista.org/guida/versione2/C14.php

Questi due metodi generics che ho scritto in un vecchio progetto possono salvare e caricare qualsiasi tipo di oggetto:
Codice sorgente - presumibilmente VB.NET

  1. ''' <summary>
  2.     ''' Serializza qualsiasi oggetto.
  3.     ''' </summary>
  4.     ''' <typeparam name="T">Un qualsiasi tipo serializzabile.</typeparam>
  5.     ''' <param name="File">Nome del file su quale serializzare l'oggetto.</param>
  6.     ''' <param name="Graph">Oggetto da serializzare.</param>
  7.     ''' <remarks>Se il file esiste, viene sovrascritto.</remarks>
  8.     Public Sub Serialize(Of T)(ByVal File As String, ByVal Graph As T)
  9.         Dim Serializer As New Runtime.Serialization.Formatters.Binary.BinaryFormatter()
  10.         Dim Stream As New IO.FileStream(File, IO.FileMode.Create)
  11.         Serializer.Serialize(Stream, Graph)
  12.         Stream.Close()
  13.         Serializer = Nothing
  14.     End Sub
  15.  
  16.     ''' <summary>
  17.     ''' Deserializza qualsiasi oggetto.
  18.     ''' </summary>
  19.     ''' <typeparam name="T">Un qualsiasi tipo serializzabile.</typeparam>
  20.     ''' <param name="File">Nome del file da cui deserializzare l'oggetto.</param>
  21.     ''' <returns>Se il file non esiste, restituisce Nothing.</returns>
  22.     Public Function Deserialize(Of T)(ByVal File As String) As T
  23.         Dim Serializer As New Runtime.Serialization.Formatters.Binary.BinaryFormatter()
  24.         Try
  25.             Dim Stream As New IO.FileStream(File, IO.FileMode.Open)
  26.             Dim Result As T
  27.             Result = Serializer.Deserialize(Stream)
  28.             Stream.Close()
  29.             Serializer = Nothing
  30.             Return Result
  31.         Catch Ex As Exception
  32.  
  33.         Finally
  34.             Serializer = Nothing
  35.         End Try
  36.         Return Nothing
  37.     End Function


Ovviamente, se devi salvare più oggetti serializzerai una lista o un array di oggetti.

PM Quote
Avatar
xeeynamo (Normal User)
Pro


Messaggi: 66
Iscritto: 14/03/2008

Segnala al moderatore
Postato alle 17:00
Giovedì, 19/11/2009
Credo di aver trovato il modo per farlo!!!
Codice sorgente - presumibilmente C#

  1. public struct Ciao
  2. {
  3.     public byte v1;
  4.     public int v2, v3;
  5.     public byte v4;
  6.     public ushort v5;
  7.     public Ciao(bool reset)
  8.     {
  9.         v1 = 1;
  10.         v2 = 0;
  11.         v3 = 0;
  12.         v4 = 0;
  13.         v5 = 0;
  14.     }
  15. }


questa è la struttura
Codice sorgente - presumibilmente C# / VB.NET

  1. byte[] data = new byte[12] { 10, 50, 60, 70, 80, 10, 20, 30, 40, 99, 12, 34 };
  2. Ciao ciao = new Ciao(true);
  3.  
  4. IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(ciao));
  5. Marshal.StructureToPtr(ciao, ptr, true);
  6. Marshal.Copy(data, 0, ptr, data.Length);



Questo è il codice... Funziona, ma il problema è che la srruttura non me la tocca proprio =( cioè che dopo questa operazione, v1 rimane uguale ad 1 e gli altri rimangono uguali a 0...

PM Quote
Avatar
Il Totem (Admin)
Guru^2


Messaggi: 3635
Iscritto: 24/01/2006

Segnala al moderatore
Postato alle 13:19
Venerdì, 20/11/2009
Certo, il marshalling è permesso con le strutture. Non l'ho scritto perchè pensavo ti bastassero tutti gli altri metodi.

Ma i valori li hai controllati in ciao? Perchè se è così, è normale: la funzione StructureToPtr copia solamente il contenuto di ciao nella memoria non gestita iniziante in ptr. Se poi gli copi dentro altri valori, è sempre quella memoria non gestita ad essere modificata. Quindi per ottenere la struttura modificata devi tornare indietro con PtrToStructure:
Codice sorgente - presumibilmente Plain Text

  1. Ciao j = (Ciao)Marshal.PtrToStructure(ptr, typeof(Ciao));



Ultima modifica effettuata da Il Totem il 20/11/2009 alle 13:20
PM Quote